Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
immutability-helper
Advanced tools
The immutability-helper npm package provides a set of utilities to perform immutable operations on JavaScript objects and arrays. It allows you to update nested data structures without mutating the original data, which is particularly useful in state management for applications like React.
Updating a value
This feature allows you to update a specific value in an object. In the example, the value of 'a' is updated from 1 to 3.
const update = require('immutability-helper');
const state = {a: 1, b: 2};
const newState = update(state, {a: {$set: 3}});
console.log(newState); // {a: 3, b: 2}
Pushing an element to an array
This feature allows you to push a new element to an array. In the example, the number 4 is pushed to the end of the array.
const update = require('immutability-helper');
const state = [1, 2, 3];
const newState = update(state, {$push: [4]});
console.log(newState); // [1, 2, 3, 4]
Splicing an array
This feature allows you to splice an array. In the example, the element at index 1 is replaced with the number 4.
const update = require('immutability-helper');
const state = [1, 2, 3];
const newState = update(state, {$splice: [[1, 1, 4]]});
console.log(newState); // [1, 4, 3]
Merging objects
This feature allows you to merge objects. In the example, a new key 'c' with value 3 is added to the object under the key 'b'.
const update = require('immutability-helper');
const state = {a: 1, b: 2};
const newState = update(state, {b: {$merge: {c: 3}}});
console.log(newState); // {a: 1, b: {c: 3}}
Applying a function
This feature allows you to apply a function to a value. In the example, the value of 'a' is doubled.
const update = require('immutability-helper');
const state = {a: 1, b: 2};
const newState = update(state, {a: {$apply: (x) => x * 2}});
console.log(newState); // {a: 2, b: 2}
Immer is a package that allows you to work with immutable state in a more intuitive way by using a 'draft' state that you can mutate directly. It then produces the next immutable state based on the mutations. Compared to immutability-helper, Immer provides a more natural way to work with immutable data by allowing direct mutations on a draft state.
Immutable.js is a library that provides immutable data structures such as List, Map, and Set. It offers a rich API for working with these data structures. Compared to immutability-helper, Immutable.js requires you to use its own data structures, which can be more complex but also more powerful for certain use cases.
Seamless-immutable is a library that provides immutable versions of JavaScript's standard data structures. It allows you to work with plain JavaScript objects and arrays while ensuring immutability. Compared to immutability-helper, seamless-immutable offers a more straightforward approach by extending native JavaScript data structures.
Mutate a copy of data without changing the original source
Setup via NPM
npm install immutability-helper --save
This is a drop-in replacement for react-addons-update
:
// import update from 'react-addons-update';
import update from 'immutability-helper';
const state1 = ['x'];
const state2 = update(state1, {$push: ['y']}); // ['x', 'y']
Note that this module has nothing to do with React. However, since this module is most commonly used with React, the docs will focus on how it can be used with React.
React lets you use whatever style of data management you want, including
mutation. However, if you can use immutable data in performance-critical parts
of your application it's easy to implement a fast shouldComponentUpdate()
method
to significantly speed up your app.
Dealing with immutable data in JavaScript is more difficult than in languages
designed for it, like Clojure. However, we've provided a
simple immutability helper, update()
, that makes dealing with this type of
data much easier, without fundamentally changing how your data is represented.
You can also take a look at Facebook's
Immutable.js and React’s
Using Immutable Data Structures section for more
detail on Immutable.js.
If you mutate data like this:
myData.x.y.z = 7;
// or...
myData.a.b.push(9);
You have no way of determining which data has changed since the previous copy
has been overwritten. Instead, you need to create a new copy of myData
and
change only the parts of it that need to be changed. Then you can compare the
old copy of myData
with the new one in shouldComponentUpdate()
using
triple-equals:
const newData = deepCopy(myData);
newData.x.y.z = 7;
newData.a.b.push(9);
Unfortunately, deep copies are expensive, and sometimes impossible. You can alleviate this by only copying objects that need to be changed and by reusing the objects that haven't changed. Unfortunately, in today's JavaScript this can be cumbersome:
const newData = Object.assign({}, myData, {
x: Object.assign({}, myData.x, {
y: Object.assign({}, myData.x.y, {z: 7}),
}),
a: Object.assign({}, myData.a, {b: myData.a.b.concat(9)})
});
While this is fairly performant (since it only makes a shallow copy of log n
objects and reuses the rest), it's a big pain to write. Look at all the
repetition! This is not only annoying, but also provides a large surface area
for bugs.
update()
update()
provides simple syntactic sugar around this pattern to make writing
this code easier. This code becomes:
import update from 'immutability-helper';
const newData = update(myData, {
x: {y: {z: {$set: 7}}},
a: {b: {$push: [9]}}
});
While the syntax takes a little getting used to (though it's inspired by MongoDB's query language) there's no redundancy, it's statically analyzable and it's not much more typing than the mutative version.
The $
-prefixed keys are called commands. The data structure they are
"mutating" is called the target.
{$push: array}
push()
all the items in array
on the target.{$unshift: array}
unshift()
all the items in array
on the target.{$splice: array of arrays}
for each item in arrays
call splice()
on
the target with the parameters provided by the item. Note: The items in
the array are applied sequentially, so the order matters. The indices of the
target may change during the operation.{$set: any}
replace the target entirely.{$toggle: array of strings}
toggles a list of boolean fields from the
target object.{$unset: array of strings}
remove the list of keys in array
from the
target object.{$merge: object}
merge the keys of object
with the target.{$apply: function}
passes in the current value to the function and
updates it with the new returned value.{$add: array of objects}
add a value to a Map
or Set
. When adding to a
Set
you pass in an array of objects to add, when adding to a Map, you pass
in [key, value]
arrays like so:
update(myMap, {$add: [['foo', 'bar'], ['baz', 'boo']]})
{$remove: array of strings}
remove the list of keys in array from a Map
or Set
.$apply
syntaxAdditionally, instead of a command object, you can pass a function, and it will
be treated as if it was a command object with the $apply
command:
update({a: 1}, {a: function})
. That example would be equivalent to
update({a: 1}, {a: {$apply: function}})
.
:warning: update
only works for data properties, not for accessor properties defined with Object.defineProperty
. It just does not see the latter, and therefore might create shadowing data properties which could break application logic depending on setter side effects. Therefore update
should only be used on plain data objects that only contain data properties as descendants.
const initialArray = [1, 2, 3];
const newArray = update(initialArray, {$push: [4]}); // => [1, 2, 3, 4]
initialArray
is still [1, 2, 3]
.
const collection = [1, 2, {a: [12, 17, 15]}];
const newCollection = update(collection, {2: {a: {$splice: [[1, 1, 13, 14]]}}});
// => [1, 2, {a: [12, 13, 14, 15]}]
This accesses collection
's index 2
, key a
, and does a splice of one item
starting from index 1
(to remove 17
) while inserting 13
and 14
.
const obj = {a: 5, b: 3};
const newObj = update(obj, {b: {$apply: function(x) {return x * 2;}}});
// => {a: 5, b: 6}
// This is equivalent, but gets verbose for deeply nested collections:
const newObj2 = update(obj, {b: {$set: obj.b * 2}});
const obj = {a: 5, b: 3};
const newObj = update(obj, {$merge: {b: 6, c: 7}}); // => {a: 5, b: 6, c: 7}
Arrays can be indexed into with runtime variables via the ES2015 Computed Property Names feature. An object property name expression may be wrapped in brackets [] which will be evaluated at runtime to form the final property name.
const collection = {children: ['zero', 'one', 'two']};
const index = 1;
const newCollection = update(collection, {children: {[index]: {$set: 1}}});
// => {children: ['zero', 1, 'two']}
// Delete at a specific index, no matter what value is in it
update(state, { items: { $splice: [[index, 1]] } });
Autovivification is the auto creation of new arrays and objects when needed. In the context of javascript that would mean something like this
const state = {}
state.a.b.c = 1; // state would equal { a: { b: { c: 1 } } }
Since javascript doesn't have this "feature", the same applies to
immutability-helper
. The reason why this is practically impossible in
javascript and by extension immutability-helper
is the following:
var state = {}
state.thing[0] = 'foo' // What type should state.thing have? Should it be an object or array?
state.thing2[1] = 'foo2' // What about thing2? This must be an object!
state.thing3 = ['thing3'] // This is regular js, this works without autovivification
state.thing3[1] = 'foo3' // Hmm, notice that state.thing2 is an object, yet this is an array
state.thing2.slice // should be undefined
state.thing2.slice // should be a function
If you need to set something deeply nested and don't want to have to set each layer down the line, consider using this technique which is shown with a contrived example:
var state = {}
var desiredState = {
foo: [
{
bar: ['x', 'y', 'z']
},
],
};
const state2 = update(state, {
foo: foo =>
update(foo || [], {
0: fooZero =>
update(fooZero || {}, {
bar: bar => update(bar || [], { $push: ["x", "y", "z"] })
})
})
});
console.log(JSON.stringify(state2) === JSON.stringify(desiredState)) // true
// note that state could have been declared as any of the following and it would still output true:
// var state = { foo: [] }
// var state = { foo: [ {} ] }
// var state = { foo: [ {bar: []} ] }
You can also choose to use the extend functionality to add an $auto
and
$autoArray
command:
import update, { extend } from 'immutability-helper';
extend('$auto', function(value, object) {
return object ?
update(object, value):
update({}, value);
});
extend('$autoArray', function(value, object) {
return object ?
update(object, value):
update([], value);
});
var state = {}
var desiredState = {
foo: [
{
bar: ['x', 'y', 'z']
},
],
};
var state2 = update(state, {
foo: {$autoArray: {
0: {$auto: {
bar: {$autoArray: {$push: ['x', 'y', 'z']}}
}}
}}
});
console.log(JSON.stringify(state2) === JSON.stringify(desiredState)) // true
The main difference this module has with react-addons-update
is that
you can extend this to give it more functionality:
import update, { extend } from 'immutability-helper';
extend('$addtax', function(tax, original) {
return original + (tax * original);
});
const state = { price: 123 };
const withTax = update(state, {
price: {$addtax: 0.8},
});
assert(JSON.stringify(withTax) === JSON.stringify({ price: 221.4 }));
Note that original
in the function above is the original object, so if you
plan making a mutation, you must first shallow clone the object. Another option
is to use update
to make the change
return update(original, { foo: {$set: 'bar'} })
If you don't want to mess around with the globally exported update
function
you can make a copy and work with that copy:
import { Context } from 'immutability-helper';
const myContext = new Context();
myContext.extend('$foo', function(value, original) {
return 'foo!';
});
myContext.update(/* args */);
FAQs
mutate a copy of data without changing the original source
We found that immutability-helper demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.